﻿#region copyright
//<copyright>
// Copyright(C) 2012 TrackerRealm Corporation
// This file is part of the open source project - Jazz. http://jazz.codeplex.com
// 
// Jazz is open software: you can redistribute it and/or modify it 
// under the terms of the GNU Affero General Public License (AGPL) as published by 
// the Free Software Foundation, version 3 of the License.
// 
// Jazz is distributed in the hope that it will be useful, 
// but WITHOUT ANY WARRANTY; without even the implied warranty 
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
//  See the GNU Affero General Public License (AGPL) for more details.
// 
// You should have received a copy of the GNU General Public 
// License along with Jazz.  If not, see <http://www.gnu.org/licenses/>.
//
// REMOVAL OF THIS NOTICE IS VIOLATION OF THE COPYRIGHT. 
//</copyright>
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Reflection;
using System.Text.RegularExpressions;

namespace TrackerRealm.Jazz.Common.DateandTime
{
    /// <summary>
    /// 
    /// </summary>
    public static class DateTimeExtensionMethods
    {
        #region Constants
        /// <summary>
        /// 
        /// </summary>
        public readonly static long TicksPerSecond = 1000L * 1000 * 10;
        /// <summary>
        /// 
        /// </summary>
        public readonly static long TicksPerMinute = 60L * TicksPerSecond;
        /// <summary>
        /// 
        /// </summary>
        public readonly static long TicksPerHour = 60L * TicksPerMinute;
        /// <summary>
        /// 
        /// </summary>
        public readonly static long TicksPerDay = 24L * TicksPerHour;
        #endregion
        //
        // int Extensions
        //
        #region int Extensions
        #region Minutes
        /// <summary>
        /// 
        /// </summary>
        /// <param name="minutes"></param>
        /// <returns></returns>
        public static TimeSpan Minutes(this int minutes)
        {
            return TimeSpan.FromMinutes(minutes);
        }
        #endregion
        #region Hours
        /// <summary>
        /// 
        /// </summary>
        /// <param name="hours"></param>
        /// <returns></returns>
        public static TimeSpan Hours(this int hours)
        {
            return TimeSpan.FromHours(hours);
        }
        #endregion
        #region Days
        /// <summary>
        /// 
        /// </summary>
        /// <param name="days"></param>
        /// <returns></returns>
        public static TimeSpan Days(this int days)
        {
            return TimeSpan.FromDays(days);
        }
        #endregion
        #region Weeks
        /// <summary>
        /// 
        /// </summary>
        /// <param name="weeks"></param>
        /// <returns></returns>
        public static TimeSpan Weeks(this int weeks)
        {
            return TimeSpan.FromDays(weeks * 7);
        }
        #endregion
        #endregion
        //
        // TimeSpan Extensions
        //
        #region TimeSpan Extensions
        #region Total String
        /// <summary>
        /// Creates a Total Duration String
        /// </summary>
        /// <returns></returns>
        public static string TotalString(this TimeSpan ts)
        {
            StringBuilder sb = new StringBuilder();
            if (ts.Days > 1)
                sb.AppendFormat("{0} days", ts.Days);
            else if (ts.Days > 0)
                sb.AppendFormat("{0} day", ts.Days);
            if (ts.Hours > 0)
            {
                if (sb.Length > 0)
                    sb.Append(", ");
                if (ts.Hours > 1)
                    sb.AppendFormat("{0} hours", ts.Hours);
                else
                    sb.AppendFormat("{0} hour", ts.Hours);
            }
            if (ts.Minutes > 0)
            {
                if (sb.Length > 0)
                    sb.Append(", ");
                if (ts.Minutes > 1)
                    sb.AppendFormat("{0} minutes", ts.Minutes);
                else
                    sb.AppendFormat("{0} minute", ts.Minutes);
            }
            if (ts.Seconds > 0)
            {
                if (sb.Length > 0)
                    sb.Append(", ");
                if (ts.Seconds > 1)
                    sb.AppendFormat("{0} seconds", ts.Seconds);
                else
                    sb.AppendFormat("{0} second", ts.Seconds);
            }
            return sb.ToString();
        }
        #endregion
        #endregion
        //
        // DateTimeOffset Extensions
        //
        #region DateTimeOffset Extensions
        /// <summary>
        /// Gets a DateTime representing the first day in the current month
        /// </summary>
        /// <param name="current">The current date</param>
        /// <returns></returns>
        public static DateTime First(this DateTime current)
        {
            DateTime first = current.AddDays(1 - current.Day);
            return first;
        }

        /// <summary>
        /// Gets a DateTime representing the first specified day in the current month
        /// </summary>
        /// <param name="current">The current day</param>
        /// <param name="dayOfWeek">The current day of week</param>
        /// <returns></returns>
        public static DateTime First(this DateTime current, DayOfWeek dayOfWeek)
        {
            DateTime first = current.First();

            if (first.DayOfWeek != dayOfWeek)
            {
                first = first.Next(dayOfWeek);
            }

            return first;
        }

        /// <summary>
        /// Gets a DateTime representing the last day in the current month
        /// </summary>
        /// <param name="current">The current date</param>
        /// <returns></returns>
        public static DateTime Last(this DateTime current)
        {
            int daysInMonth = DateTime.DaysInMonth(current.Year, current.Month);

            DateTime last = current.First().AddDays(daysInMonth - 1);
            return last;
        }

        /// <summary>
        /// Gets a DateTime representing the last specified day in the current month
        /// </summary>
        /// <param name="current">The current date</param>
        /// <param name="dayOfWeek">The current day of week</param>
        /// <returns></returns>
        public static DateTime Last(this DateTime current, DayOfWeek dayOfWeek)
        {
            DateTime last = current.Last();

            last = last.AddDays(Math.Abs(dayOfWeek - last.DayOfWeek) * -1);
            return last;
        }

        /// <summary>
        /// Gets a DateTime representing the first date following the current date which falls on the given day of the week
        /// </summary>
        /// <param name="current">The current date</param>
        /// <param name="dayOfWeek">The day of week for the next date to get</param>
        public static DateTime Next(this DateTime current, DayOfWeek dayOfWeek)
        {
            int offsetDays = dayOfWeek - current.DayOfWeek;

            if (offsetDays <= 0)
            {
                offsetDays += 7;
            }

            DateTime result = current.AddDays(offsetDays);
            return result;
        }
        #region Days
        /// <summary>
        /// Returns the number of days that the time period spans.
        /// </summary>
        /// <returns></returns>
        public static int Days(this DateTimeOffset dt, DateTimeOffset end)
        {
            return (end.DateOffset() - dt.DateOffset()).Days + 1;
        }
        #endregion
        #region Weekdays
        /// <summary>
        /// Returns the number of week days that the time period spans.
        /// <para>This is not the number of 24 periods, but the number of
        /// times this 'timeperiod' spans 12 midnight to a weekday.</para>
        /// </summary>
        /// <param name="dt"></param>
        /// <param name="end"></param>
        /// <returns></returns>
        public static int WeekDays(this DateTimeOffset dt, DateTimeOffset end)
        {
            int noOfDays = (end.DateOffset() - dt.DateOffset()).Days + 1;
            int oddDays = noOfDays % 7;
            if (oddDays > 0)
            {
                //DateTimeEnd.DayOfWeek
                switch (dt.DayOfWeek)
                {
                    case DayOfWeek.Friday:
                        if (oddDays >= 3) oddDays = oddDays - 2;
                        else if (oddDays >= 2) oddDays = oddDays - 1;
                        break;
                    case DayOfWeek.Monday:
                        if (oddDays >= 6) oddDays = oddDays - 1;
                        break;
                    case DayOfWeek.Saturday:
                        oddDays = oddDays - 2;
                        break;
                    case DayOfWeek.Sunday:
                        oddDays = oddDays - 1;
                        break;
                    case DayOfWeek.Thursday:
                        if (oddDays >= 4) oddDays = oddDays - 2;
                        else if (oddDays >= 3) oddDays = oddDays - 1;
                        break;
                    case DayOfWeek.Tuesday:
                        if (oddDays >= 6) oddDays = oddDays - 2;
                        else if (oddDays >= 5) oddDays = oddDays - 1;
                        break;
                    case DayOfWeek.Wednesday:
                        if (oddDays >= 5) oddDays = oddDays - 2;
                        else if (oddDays >= 4) oddDays = oddDays - 1;
                        break;
                    default:
                        break;
                }
                if (oddDays < 0) oddDays = 0;
                else
                    if (oddDays > 5) oddDays = 5;

            }
            return ((noOfDays / 7) * 5) + oddDays;
        }
        #endregion
        #region AddWeekDays
        /// <summary>
        /// *** Under construction****
        /// Returns a DateTimeOffset with the specified number of week days added to
        /// 'this' DateTimeOffset.
        /// </summary>
        /// <param name="dt"></param>
        /// <param name="noOfWeekDays"></param>
        /// <returns></returns>
        public static DateTimeOffset AddWeekDays(this DateTimeOffset dt, int noOfWeekDays)
        {
            int noOfWeeks = noOfWeekDays / 5;
            int oddDays = noOfWeekDays % 5;
            if (oddDays > 0)
            {
                switch (dt.DayOfWeek)
                {
                    case DayOfWeek.Friday:
                        oddDays = oddDays + 2;
                        break;
                    case DayOfWeek.Monday:
                        break;
                    case DayOfWeek.Saturday:
                        oddDays = oddDays + 1;
                        break;
                    case DayOfWeek.Sunday:
                        break;
                    case DayOfWeek.Thursday:
                        if (oddDays >= 2) oddDays = oddDays + 2;
                        break;
                    case DayOfWeek.Tuesday:
                        if (oddDays >= 4) oddDays = oddDays + 2;
                        break;
                    case DayOfWeek.Wednesday:
                        if (oddDays >= 3) oddDays = oddDays + 2;
                        break;
                    default:
                        throw new NotImplementedException();
                }
            }
            return dt + noOfWeeks.Weeks() + oddDays.Days();
        }
        #endregion


        /// <summary>
        /// Returns the only the date portion of the 'DateTimeOffset' object.
        /// </summary>
        /// <param name="dt"></param>
        /// <returns></returns>
        public static DateTimeOffset DateOffset(this DateTimeOffset dt)
        {
            return new DateTimeOffset(dt.Date, dt.Offset);
        }
        
        
        /// <summary>
        /// Rounds down the 'DateTimeOffset' to the nearest 30 minutes.
        /// </summary>
        /// <param name="dt"></param>
        /// <returns></returns>
        public static DateTimeOffset RoundDownTo30Minutes(this DateTimeOffset dt)
        {
            long ticks = dt.Ticks % TicksPerHour;
            if (ticks <= 30 * TicksPerMinute)
                return new DateTimeOffset(dt.Year, dt.Month, dt.Day, dt.Hour, 0, 0, dt.Offset);
            return new DateTimeOffset(dt.Year, dt.Month, dt.Day, dt.Hour, 30, 0, dt.Offset);
        }
        /// <summary>
        /// Rounds down the 'DateTimeOffset' to the nearest number of minutes
        /// specified by the paramter 'ts'.
        /// </summary>
        /// <param name="dt"></param>
        /// <param name="ts"></param>
        /// <returns></returns>
        public static DateTimeOffset RoundDownToMinutes(this DateTimeOffset dt, TimeSpan ts)
        {
            if (ts.Ticks < TicksPerMinute || ts.Ticks > (TicksPerHour))
                throw new ArgumentOutOfRangeException("ts", "Timespan must be 1 minute or greater and Timespan must be 1 hour or less.");
            if (ts.Ticks % TicksPerMinute > 0)
                throw new ArgumentException("ts", "Timespan must be specified in a number of minutes (no seconds).");
            if (ts.Minutes != 0)
            {
                int remainder = dt.Minute % (ts.Minutes);

                return new DateTimeOffset(dt.Year, dt.Month, dt.Day, dt.Hour, (dt.Minute - remainder), 0, dt.Offset);
            }
            else
            {
                return new DateTimeOffset(dt.Year, dt.Month, dt.Day, dt.Hour, 0, 0, dt.Offset);
            }
        }
        
        /// <summary>
        /// Returns a string of the format 'ticks:offset'.
        /// <para>
        /// Note:  That the formated string can be parsed by 'string.ParseDateTimeOffset'.</para>
        /// <para>See also 'ToStringVerbose'</para>
        /// </summary>
        /// <param name="dt"></param>
        /// <returns></returns>
        public static string ToStringNumeric(this DateTimeOffset dt)
        {
            return dt.Ticks.ToString() + ":" + dt.Offset.Hours.ToString();
        }
        /// <summary>
        /// Returns a string of the format 'yyyy:mm:dd:ss:fffff:offset'.
        /// <para>
        /// Note:  That the formated string can be parsed by 'string.ParseDateTimeOffset'.</para>
        /// <para>See also 'ToStringNumeric'</para>
        /// </summary>
        /// <param name="dt"></param>
        /// <returns></returns>
        public static string ToStringVerbose(this DateTimeOffset dt)
        {
            return dt.ToString("yyyy:MM:dd:HH:mm:ss:fffffff") + ":" + dt.Offset.Hours.ToString();
        }
        #endregion
    }
}
